Skip to content

fix: migrate Gemini Interactions emitter to SDK 2.x event protocol (#277)#279

Open
tombeckenham wants to merge 3 commits into
CopilotKit:mainfrom
tombeckenham:277-gemini-interactions-emitter-still-produces-sdk-1-x-events-migrate-to-sdk-2-x-step-interaction-created-completed
Open

fix: migrate Gemini Interactions emitter to SDK 2.x event protocol (#277)#279
tombeckenham wants to merge 3 commits into
CopilotKit:mainfrom
tombeckenham:277-gemini-interactions-emitter-still-produces-sdk-1-x-events-migrate-to-sdk-2-x-step-interaction-created-completed

Conversation

@tombeckenham

Copy link
Copy Markdown
Contributor

Summary

Migrates the Gemini Interactions mock emitter from the SDK 1.x streamed-event format to SDK 2.x (the "Interactions breaking changes, May 2026" shapes in @google/genai v2). Previously the emitter produced interaction.start/interaction.complete + content.start/content.delta/content.stop, which have zero event_type overlap with the v2 adapter — a migrated consumer hit its switch default for every event and rendered an empty assistant message.

Closes #277.

What changed

src/gemini-interactions.ts — rewrote the three SSE builders:

  • Lifecycle: interaction.start/completeinteraction.created/completed (id stays populated on both; status text→completed, tool calls→requires_action).
  • Text: content.start/delta/stopstep.start { step: { type: "model_output" } } / step.delta { delta: { type: "text", text } } / step.stop.
  • Tool calls: call identity (id, name) now lives on step.start with an arguments: {} placeholder; arguments stream as step.delta { delta: { type: "arguments_delta", arguments: "<json-string fragment>" } }, valid JSON by step.stop.
  • writeGeminiInteractionsSSEStream now counts step.delta (not content.delta) for the truncateAfterChunks budget.

src/stream-collapse.tscollapseGeminiInteractionsSSE now parses 2.x events (assemble tool calls from step.start identity + arguments_delta string fragments keyed by index; nested thought_summary.content.text), while keeping 1.x parsing for previously recorded fixtures. Hardened against silent data loss: malformed assembled args and index-less / uncorrelated step.start/arguments_delta are flagged via droppedChunks/firstDroppedSample rather than written silently.

Tests + drift baseline — updated unit/integration/collapse tests and src/__tests__/drift/sdk-shapes.ts to 2.x; added round-trip, multiple/interleaved tool calls, ordering, malformed-args, usage/status, and 1.x backward-compat coverage.

Verification

grep -E 'event_type: "(step|interaction\.created|interaction\.completed)' dist/gemini-interactions.js

matches after build (20 hits, 0 legacy shapes remain). Full suite: 4026 passing, prettier + eslint clean.

This unblocks re-enabling the stateful-interactions e2e test in TanStack/ai#781 once aimock is bumped.

Notes / scope

  • The non-streaming JSON response builders (outputs: [{ type: "function_call", ... }]) were intentionally left unchanged — the issue scoped the wire-format mismatch to the streamed SSE emitter.
  • One ambiguous case is deliberately not signalled: a streamed function_call whose step.start carries an empty {} placeholder but never receives an arguments_delta finalizes to "{}". On the wire that is byte-identical to a legitimately empty-args call, so flagging it would false-positive on every legitimate empty-args call.

🤖 Generated with Claude Code

The Interactions mock emitter produced the SDK 1.x streamed event shapes
(interaction.start/complete, content.start/delta/stop), which have zero
event_type overlap with the SDK 2.x adapter (@google/genai v2 'Interactions
breaking changes, May 2026'). A v2 consumer hit its switch default for every
event and rendered an empty assistant message.

- Rewrite the three SSE builders to emit interaction.created/completed and
  step.start/delta/stop. Tool-call identity (id/name) now lives on step.start
  with an empty arguments placeholder; arguments stream as a dedicated
  arguments_delta carrying a JSON-string fragment (valid JSON by step.stop).
- Count step.delta (not content.delta) for truncateAfterChunks budget.
- Teach collapseGeminiInteractionsSSE the 2.x shapes (step.start identity +
  arguments_delta assembly, nested thought_summary), keeping 1.x parsing for
  backward compatibility with previously recorded fixtures.
- Update unit/integration/collapse tests and drift SDK shapes to 2.x; add
  round-trip and legacy backward-compat coverage.

Closes CopilotKit#277
Address review findings on the SDK 2.x migration:
- Flag assembled arguments_delta fragments that don't concatenate into valid
  JSON by step.stop via droppedChunks/firstDroppedSample, instead of silently
  writing a corrupt tool call into a recorded fixture.
- Preserve the identity of a function_call step.start that arrives without an
  index by minting a synthetic key (matches the sibling collapsers) rather
  than dropping it silently.
- Reword the emitter's arguments_delta comment: the mock emits one whole
  fragment (a valid degenerate case), it does not itself concatenate.
- Add tests: multiple/interleaved tool calls collapse in step-index order,
  emitter assigns sequential step indices, invalid-JSON assembly is flagged,
  and an index-less function_call step.start keeps its identity.
- Streaming builders: malformed tool-call arguments degrade to a valid '{}'
  arguments_delta fragment and emit a warning.
- Tool-call and content+tools streams: assert usage propagates and the
  terminal status is requires_action on interaction.completed (unit + e2e).
- Collapser: pin the ordering behavior when an arguments_delta arrives before
  its step.start — the early fragment is flagged via droppedChunks rather than
  mis-attributed, and the call still surfaces with empty args.
@pkg-pr-new

pkg-pr-new Bot commented Jun 24, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@copilotkit/aimock@279

commit: 24de47f

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

gemini-interactions: emitter still produces SDK 1.x events; migrate to SDK 2.x step.* / interaction.created|completed

1 participant